
#include "socklib_ipv6.h"

t_ligne *ligne_new(int taille_dep) {
	t_ligne *res = (t_ligne*) calloc(sizeof(t_ligne), 1);

  if (res == NULL) {
    fprintf(stderr, "%s %d newligne %s",
	    __FILE__, __LINE__, strerror(errno));
    return NULL;
  }

  res->taille = 0;
  res->taille_reservee = taille_dep;
  if (taille_dep != 0) {
    res->tab = calloc(taille_dep,1);
    if (res->tab == NULL) {
      fprintf(stderr, "%s %d newligne %s",
	      __FILE__, __LINE__, strerror(errno));
      free(res);
      return NULL;
    }
  } else {
    res->tab = NULL;
  }

  return res;
}

void ajout_ligne(t_ligne *l, const void *tab, const int tailletab) {
  if (tailletab+l->taille > l->taille_reservee) {
    void *temp = realloc(l->tab, 2*l->taille_reservee);
    if (temp == NULL) {
      fprintf(stderr, "%s %d impossible d'ajouter des donnée %s",
	      __FILE__, __LINE__, strerror(errno));
      return ;
    }
    l->taille_reservee = 2*l->taille_reservee;
    l->tab = temp;
  }

  memcpy(&(l->tab[l->taille]), tab, tailletab);
  l->taille+=tailletab;
}

void sup_ligne(t_ligne *l, int free_tab) {
  if (free_tab) {
    free(l->tab);
  }
  l->tab = NULL;
  free(l);
}

int init_connect(const char *port, const char * host) {
	/*structure pour faire la demande */
    	struct addrinfo hints;
    	/*structure pour stocker et lire les rÃ©sultats*/
    	struct addrinfo *result, *rp;
    	/*socket  (s)*/
    	int s=-1;
    	/*variables pour tester si les fonctions donnent un rÃ©sultats ou une erreur*/
    	int res;
    	int bon;
    	/*Les variable pour contenir de adresse de machine et des numero de port afin de les afficher*/
    	char hname[NI_MAXHOST], sname[NI_MAXSERV];


    	/*On rempli la structure hints de demande d'adresse*/
    	memset(&hints, 0, sizeof(struct addrinfo));
    	hints.ai_family = AF_UNSPEC;    /* IPv4 ou IPv6 */
    	hints.ai_socktype = SOCK_STREAM; /* socket flux connectÃ©e */
    	hints.ai_flags = 0;
    	hints.ai_protocol = 0;          /* Any protocol */
    	hints.ai_addrlen = 0;
    	hints.ai_addr = NULL;
    	hints.ai_canonname = NULL;
    	hints.ai_next = NULL;
	
    	res = getaddrinfo(host, port, &hints, &result);
    	if (res != 0) { /*c'est une erreur*/
        	fprintf(stderr, "ERR: getaddrinfo: %s\n", gai_strerror(s));
        	exit(1);
    	}

    	/*si res = 0 le vÃ©ritable rÃ©sultat de la fontion est l'argument result
     	qui contient une liste d'addresse correspondant Ã  la demande on va les
     	rester jusqu'a trouver une qui convient*/
    	rp = result;
    	bon = 0;
    	while (rp != NULL) {
        	/*on parcourt la liste pour en trouver une qui convienne*/

        	/*on rÃ©cupÃ¨re des informations affichables*/
        	res = getnameinfo(rp->ai_addr, rp->ai_addrlen,
                hname, NI_MAXHOST,
                sname, NI_MAXSERV,
                NI_NUMERICSERV|NI_NUMERICHOST);
        	if (res != 0) {
            		fprintf(stderr, "getnameinfo: %s\n", gai_strerror(res));
            		exit (1);
        	}

        	/*on essaye*/
        	s = socket(rp->ai_family, rp->ai_socktype,rp->ai_protocol);
        	/*si le rÃ©sultat est -1 cela n'a pas fonctionnÃ© on recommence avec la prochaine*/
        	if (s == -1) {
            		perror("CrÃ©ation de la socket");
            		continue;
        	}

        	/*si la socket a Ã©tÃ© obtenue, on essaye de se connecter*/
        	res = connect(s, rp->ai_addr, rp->ai_addrlen);
        	if (res == 0 ) { /*cela a fonctionnÃ© on est connectÃ©*/
            		bon = 1;
            		return s;
        	} else { /*sinon le bind a Ã©tÃ© impossible, il faut fermer la socket*/
            		perror("ERR: Impossible de se connecter");
            		close (s);
        	}

        	rp = rp->ai_next;
    	}

    	if (bon == 0) { /*Cela n'a jamais fonctionnÃ©*/
        	fprintf(stderr, "ERR: Aucune connexion possible\n");
        	exit(1);
    	}
    	freeaddrinfo(result);           /*No longer needed*/
    	fprintf(stdout, "bye\n");	
	return 0;
}

int EnvoieMessage(int s , char* format , ... ) {
	int i ,res ,errno;

    	va_list liste_des_arguments ;
    	va_start( liste_des_arguments , format ) ;

	/*on calcul la taille du message*/
    	int taille = vsnprintf(NULL, 0 , format , liste_des_arguments ) ;
    	va_end( liste_des_arguments ) ;
	/*un tableau un peu plus grand pour le \0*/
   	 char chaine [ taille+1] ;
    	va_start(liste_des_arguments , format ) ;
    	vsnprintf( chaine , taille+1, format , liste_des_arguments ) ;
    	va_end ( liste_des_arguments ) ;
    	i = 0 ;
    	while(i < taille ) { /*attention , il ne faut pas envoyer le \0*/
        	res = send( s , chaine+i , taille-i , MSG_NOSIGNAL) ;
        	if( res <=0) {
            		fprintf( stderr , "error: write %s car %s\n" , chaine , strerror(errno) ) ;
            		return -1;
        	}
        	i += res ;
    	}
    	return i ;
}

void affichage(char *buff) {	
    int i =0;
    while(buff[i] != '\n') {
        printf("%c",buff[i]);
        i++;
    }
    printf("\n");

}

/*cette fonction permet de recuperer le port a partir de resultat de la commande PASV*/
int donnePort(char *buff) {
	int i = 0;
    	int j = 0;
    	int k = 0;
    	int valPort =0;

    	while(buff[i] != '\n') {
        	i++;
    	}
    	j = i -3;

   	while(buff[j] != ',') {
        	k = atoi(&buff[j]);
        	j--;
    	}
    	valPort =k;
    	j--;
    	while(buff[j] != ',') {
        	k = atoi(&buff[j]);
        	j--;
    	}
    	valPort += (k*256);

    	return valPort;
}

int RecoieDonnees (int fd , int s ) {
    int test=1;
    char buff [TAILLE_BUFF ] ;
    int res , res2 ;
    int nbrecu = 0 ;
    printf("\n");
    while(test) {
        res = recv ( s , buff , TAILLE_BUFF, 0 ) ;
        if(res < 0) {
            perror ( "Probleme a la lecture du fichier" ) ;
            return -1;
        }
        if(res == 0) {
            // Le fichier est termine
            test = 0 ;
        }
        nbrecu += res ;
        //fprintf(stderr , "\rRecu %d oct total %d oct \n" , res , nbrecu ) ;
        if(res != 0) {
            res2 = write ( fd , buff , res ) ;
        }
        if(res != res2 ) {
            perror ( "Probleme a l’ecriture du fichier" ) ;
            return -1;
        }
    }
    return nbrecu ;
}

void envoieDonnees(int fd, int s) {
    int nb=0;
    char buff[TAILLE_BUFF];
    int res ;
    while((res = read(fd,buff,TAILLE_BUFF))>0) {    
       if( (res = send(s,buff,res,0))<0) {                           
            perror("Problème a l'envoie du fichier");
            exit(1);
        }
        nb+=res;
        fprintf(stdout,"\r %d oct  envoyés",nb);
    }
    if(close(s) < 0) {
        perror("probleme sur la fermeture de socket de donnée");
    }
}

char* recoie_donnees(int s){
    int match;
    int err;
    regex_t preg;
   
    const char *str_regex = "([0-9]{3} )";
   
    char buff[TAILLE_BUFF];
    t_ligne *result = ligne_new(TAILLE_BUFF);
    int res;
    int fini = false;
   
    while(!fini)
    {
        res = (int)recv(s, buff, 1000, MSG_PEEK);
        if (res < 0)
        {
            perror("Probleme à la lecture de la socket");
            return NULL;
        }
        if (res == 0)
        {
            // la socket est coupée
            break;
        }
	buff[res] = '\0';
        err = regcomp (&preg, str_regex, REG_EXTENDED);
        if (err == 0)
        {
            size_t nmatch = 0;
            regmatch_t *pmatch = NULL;
           
            nmatch = preg.re_nsub;
            pmatch = malloc (sizeof (*pmatch) * nmatch);
            if (pmatch)
            {
                match = regexec (&preg, buff, nmatch, pmatch, 0);
                regfree (&preg);
            }
        }
        if (match == 0)
            fini = 1;
       
        ajout_ligne(result, buff, res);
       
        // il faut penser à retirer les valeur lues du buffer
        res = (int)recv(s, buff, res, 0);
        if (res < 0)
        {
            perror("second recv");
            return NULL;
        }
        if (res == 0)
        {
            perror("second recv renvoie 0");
            return NULL;
        }
    }
   
    // on ajoute un \0 à la fin pour le cas ou ce qu'on a lu est une chaine
    ajout_ligne(result, "\0", 1);
   
    char * resu = result->tab;
   
   sup_ligne(result, false);
    return resu;
}

/*une fonction permet de s'authentifier qui prend en parametre la socket et userName et password*/
void login(int s,char *user,char *pass) {
	char buff[TAILLE_BUFF];
    	int res;

    	if ((res = EnvoieMessage(s,"%s %s%s","USER",user,"\r\n"))<0) {    /*envoie du nom d'utilisateur*/
        	perror("Probleme a l'envoie du message");
    	}
    	if ((res = recv(s,buff,TAILLE_BUFF,0))<0) {                       /*confirmation de la recetpion du nom d'utilisateur*/
        	perror("Probleme a la reception du message");
    	}
    	else {
        	//printf("%s\n",buff);
    	}
    	if ((res = EnvoieMessage(s,"%s %s%s","PASS",pass,"\r\n"))<0) {    /*envoie du mot de passe*/
       		perror("Probleme a l'envoie du message");
    	}
    	if ((res = recv(s,buff,TAILLE_BUFF,0))<0) {                      /*confirmation de l'authentification*/
        	perror("Probleme a la reception du message");
    	}
    	else {
        	//printf("%s\n",buff);
	    }
    	if((buff[0] == '2') && (buff[1] == '3') && (buff[2] == '0')) {
        	fprintf(stdout,"OK : Vous etes connecte \n\n");
    	}
    	else {
		fprintf(stderr,"ERR: probleme d'authentification\n");
		exit(1);
    }	 
}



/* une fonction pour recuperer le resultat de la commande LIST */
void connectDonnee(char * port, char * ip, int s, char * commande) {
	char buff[TAILLE_BUFF];
        int res, socket2;
        socket2 = init_connect(port,ip);
        /* on a besoin d'utiliser une 2eme socket pour se connecter sur le port de mode passif */
        if ((res = EnvoieMessage(s,"%s",commande))<0) {
        	perror("Probleme a l'envoie du message");
    	}
    	if ((res = recv(s,buff,TAILLE_BUFF,0))<0) {
        	perror("Probleme a l'envoie du message");
    	}
    	if(buff[0] == '1') {
        	RecoieDonnees(STDOUT_FILENO, socket2 );
		/*si le premier caractere est 1 alors on peut recevoir les donnes de la commande*/
        	if (close(socket2)< 0) {
            		perror("Probleme a la fermeture de la socket");
        	}
        	res = recv(s,buff,TAILLE_BUFF,0);
        	if(buff[0] == '2') {
			/* ok si c'est 2 alors on a finis avec succes*/
            		fprintf(stdout, "\nOK: Contenu du repertoire recu. \n\n");
        	} else if(buff[0] == '5') {
            		fprintf(stderr, "\nFAIL: Le contenu du repertoire n'a pas ete recu dans son integralite.\n\n");
        	} else affichage(buff);
    	} else if(buff[0] == '5') {
        	fprintf(stderr,"\nFAIL: Repertoire inexistant. \n\n");
    	} else affichage(buff);
}


void connectRETR(char * port, char * ip, int s,char * commande, char * fichier){
	
	FILE *fic=fopen(fichier,"wb");
    	char buff[TAILLE_BUFF];
    	int res, socket2;
	int fileDescriptor;
	socket2 = init_connect(port,ip);
	if ((res = recv(s,buff,TAILLE_BUFF,0))<0) 
	{
		perror("Problème de connexion");
	}
	fileDescriptor = fileno(fic);
        RecoieDonnees(fileDescriptor,socket2 );
	fclose(fic);
	res = recv(s,buff,TAILLE_BUFF,0);
	affichage(buff);

}

void connectSTOR(char * port, char * ip, int s,char * commande, char * fichier){
	FILE *fic=fopen(fichier,"rb");
	char* buff;
    	int socket2,res;
	int fileDescriptor;
	socket2 = init_connect(port,ip);
	buff = recoie_donnees(s);
	fprintf(stdout,"%s\n",buff);
	free(buff);buff=NULL;
	fileDescriptor = fileno(fic);
	envoieDonnees(fileDescriptor, socket2);
	fclose(fic);
	res = recv(s,buff,TAILLE_BUFF,0);
	affichage(buff);
}

void connectSTOR_SSL(char * port, char * ip, SSL *ssl,char * commande, char * fichier){
	FILE *fic=fopen(fichier,"rb");
    	int socket2;
	int fileDescriptor;
	socket2 = init_connect(port,ip);
	recoie_donnees_SSL(ssl);
	fileDescriptor = fileno(fic);
	envoieDonnees(fileDescriptor, socket2);
	fclose(fic);
	recoie_donnees_SSL(ssl);
}

void connectLIST(char * port, char * ip, int s, char * commande){
    	char buff[TAILLE_BUFF];
    	int res, socket2;
	socket2 = init_connect(port,ip);
	if ((res = recv(s,buff,TAILLE_BUFF,0))<0) 
	{
		perror("Problème de connexion");
	}
	affichage(buff);
        RecoieDonnees(STDOUT_FILENO,socket2 );
	if ((res = recv(s,buff,TAILLE_BUFF,0))<0) 
	{
		perror("Problème de connexion");
	}
	affichage(buff);	
}

char * recoie_donnees_SSL(SSL* ssl){
	//int taille = 0;
    	int match;
    	int err;
    	regex_t preg;
    	const char *str_regex = "([0-9]{3} )";
    	char buff[TAILLE_BUFF];
    	t_ligne *result = ligne_new(TAILLE_BUFF);
    	int res;
    	//char last = ' ';
   	int fini = 0;
   
    	while(!fini) {
        	res = SSL_read(ssl, buff, 1000);
        	if (res < 0) {
            		perror("Probleme à la lecture de la socket");
            		return NULL;
        	}
        	if (res == 0) {
            		// la socket est coupée
            		break;
        	}
      		buff[res] = '\0';
        	err = regcomp (&preg, str_regex, REG_EXTENDED);
        	if (err == 0) {
            		size_t nmatch = 0;
            		regmatch_t *pmatch = NULL;           
            		nmatch = preg.re_nsub;
            		pmatch = malloc (sizeof (*pmatch) * nmatch);
            	if (pmatch) {
                	match = regexec (&preg, buff, nmatch, pmatch, 0);
                	regfree (&preg);
            	}
        }
        if (match == 0)
        	fini = 1;
       
         // i represente le nombre d'octet à stocker.
        ajout_ligne(result, buff, res);
	affichage(buff);
        // il faut penser à retirer les valeur lues du buffer
    	}
   
    	// on ajoute un \0 à la fin pour le cas ou ce qu'on a lu est une chaine
    	ajout_ligne(result, "\0", 1);    
    	char *resu = result->tab;    
    	sup_ligne(result, 0);
    	return resu;
}

int EnvoieMessageSSL(SSL *ssl, char* format, ...){
	int i;
        int res;

        va_list liste_des_arguments;
        va_start(liste_des_arguments, format);

        /*on calcul la taille du message*/
        int taille = vsnprintf(NULL, 0, format, liste_des_arguments);
        va_end(liste_des_arguments);

        /*un tableau un peu plus grand pour le \0*/
        char chaine[taille+1];

        va_start(liste_des_arguments, format);
        vsnprintf(chaine, taille+1, format, liste_des_arguments);
        va_end(liste_des_arguments);

        /*fprintf(stderr, "Envoie %s\n", chaine);*/
        i = 0;
        while (i < taille) { // attention, il ne faut pas envoyer le \0*/
        	res = SSL_write(ssl, chaine+i, taille-i);
    		if(res<=0) {
      			fprintf(stderr, "error: write %s\n", chaine);
      			/*ERR_print_errors_fp(stderr);*/
      			return -1;
    		}
    		i += res;
	}
	return i;
}

void connectRETR_SSL(char * port, char * ip,SSL *ssl, char * commande, char * fichier){
	
	FILE *fic=fopen(fichier,"wb");
    	int  socket2;
	int fileDescriptor;

	socket2 = init_connect(port,ip);

	recoie_donnees_SSL(ssl);


	fileDescriptor = fileno(fic);
        RecoieDonnees(fileDescriptor, socket2 );
	fclose(fic);

	recoie_donnees_SSL(ssl);
	}

void login_SSL(SSL* ssl,char *user,char *pass){
	char *buff;
    	int res;

    	if ((res = EnvoieMessageSSL(ssl,"%s %s%s","USER",user,"\r\n"))<0) {    /*envoie du nom d'utilisateur*/
        	perror("Probleme a l'envoie du message");
    	}
	recoie_donnees_SSL(ssl);

    	if ((res = EnvoieMessageSSL(ssl,"%s %s%s","PASS",pass,"\r\n"))<0) {    /*envoie du mot de passe*/
       		perror("Probleme a l'envoie du message");
    	}
	buff=recoie_donnees_SSL(ssl);

    	if((buff[0] == '2') && (buff[1] == '3') && (buff[2] == '0')) {
        	fprintf(stdout,"OK: Felicitation connexion etablie \n\n");
    	}
    	else {
		fprintf(stderr,"ERR: probleme d'authentification\n");
		exit(1);
    }	 
}

void connectDonnee_ssl(char * port, char * ip,SSL *ssl, char * commande) {
	char *buff;
        int res, socket2;
        socket2 = init_connect(port,ip);
        /* on a besoin d'utiliser une 2eme socket pour se connecter sur le port de mode passif */
        if ((res = EnvoieMessageSSL(ssl,"%s",commande))<0) {
        	perror("Probleme a l'envoie du message");
    	}
	buff=recoie_donnees_SSL(ssl);
	/*
    	if ((res = recv(s,buff,TAILLE_BUFF,0))<0) {
        	perror("Probleme a l'envoie du message");
    	}*/
    	if(buff[0] == '1') {
        	RecoieDonnees(STDOUT_FILENO, socket2 );
		/*si le premier caractere est 1 alors on peut recevoir les donnes de la commande*/
        	if (close(socket2)< 0) {
            		perror("Probleme a la fermeture de la socket");
        	}
		buff=recoie_donnees_SSL(ssl);
        	//res = recv(s,buff,TAILLE_BUFF,0);
        	if(buff[0] == '2') {
			/* ok si c'est 2 alors on a finis avec succÃ¨s*/
            		fprintf(stdout, "\nOK: Contenu du repertoire recu. \n\n");
        	} else if(buff[0] == '5') {
            		fprintf(stderr, "\nFAIL: Le contenu du repertoire n'a pas ete recu dans son integralite.\n\n");
        	} else affichage(buff);
    	} else if(buff[0] == '5') {
        	fprintf(stderr,"\nFAIL: Repertoire inexistant. \n\n");
    	} else affichage(buff);
}

void connectLIST_SSL(char * port, char * ip,SSL *ssl, char * commande){
    	char buff[TAILLE_BUFF];
    	int res, socket2;
	socket2 = init_connect(port,ip);
	if ((res = SSL_read(ssl, buff, 1000))<0) 
	{
		perror("Problème de connexion");
	}
	affichage(buff);
	RecoieDonnees(STDOUT_FILENO, socket2 );
	if ((res = SSL_read(ssl, buff, 1000))<0) 
	{
		perror("Problème de connexion");
	}
	affichage(buff);
	
}

SSL * chiffrerSSL(int s){
	//char reponse[TAILLE_BUFF];
    	const SSL_METHOD* method = SSLv23_client_method();
    	SSL_CTX* ctx = NULL;
    	SSL *ssl = NULL;
    	
	SSL_library_init();
    	SSL_load_error_strings();
    	ctx = SSL_CTX_new(method);
    	
	if (ctx == NULL) {
        	//ERR_print_errors_fp(stderr);
   	}

    	SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);
    	ssl = SSL_new(ctx);
    	if (!ssl) {
        	//ERR_print_errors_fp(stderr);
        	exit(1);
    	}


        if (!SSL_set_fd(ssl, s)) {
            //ERR_print_errors_fp(stderr);
            	return NULL;
        }
        if (SSL_connect((SSL*)ssl) < 1) {
            //ERR_print_errors_fp(stderr);
            	return NULL;
        }
	if(ssl){
        	fprintf(stderr, "OK: TLS accepted\n");
    	}
    	else {
        	fprintf(stderr, "ERR: Auth TLS refus\n");
        	return NULL;
    	}
    	return ssl;
}

void aide()
{       printf(" \n");
    printf("************************CHOISIR UNE COMMANDE************************************ \n");
    printf("*                                                                              *\n");
    printf("*  LIST      pour lister le contenu du repertoire courant                      *\n");
    printf("*  RETR      filename pour telecharger le fichier filename                     *\n");
    printf("*  CWD      dirname pour changer le repertoire courant sur le serveur          *\n");
    printf("*  QUIT      pour se d ́econnecter proprement du serveur                        *\n");
    printf("*  STOR  Accepte les donnees et les enregistre dans un fichier sur le serveur  *\n");
    printf("*                                                                              *\n");
    printf("******************************************************************************** \n");
}


















